package org.hako.recite.word.input;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hako.recite.word.util.IOUtils;

/**
 * Convert roma to nihongo.
 * 
 * @since 0.4.4
 * @author <a href="mailto:xnnyygn@gmail.com">xnnyygn</a>
 */
public class RomaToNihongoConverter {

	private List<PrefixMatcher> matchers;

	/**
	 * Create
	 */
	public RomaToNihongoConverter() {
		this(RomaToNihongoConverter.class.getClassLoader().getResourceAsStream(
				"org/hako/recite/word/input/roma-nihongo-mapping.properties"));
	}

	/**
	 * Create
	 * 
	 * @param configPath
	 * @throws FileNotFoundException
	 */
	public RomaToNihongoConverter(String configPath)
			throws FileNotFoundException {
		this(new File(configPath));
	}

	/**
	 * Create
	 * 
	 * @param config
	 * @throws FileNotFoundException
	 */
	public RomaToNihongoConverter(File config) throws FileNotFoundException {
		this(new FileInputStream(config));
	}

	/**
	 * Create from input stream. This method will close stream automatically.
	 * 
	 * @param stream
	 */
	public RomaToNihongoConverter(InputStream stream) {
		this(loadPairsFromStream(stream));
	}

	/**
	 * Load pairs from stream.
	 * 
	 * @param stream
	 * @return
	 */
	private static Map<String, String> loadPairsFromStream(InputStream stream) {
		Map<String, String> pairMap = new HashMap<String, String>();
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(stream));
			String line = null;
			while ((line = reader.readLine()) != null) {
				String trimmedLine = line.trim();
				if (trimmedLine.length() == 0 || trimmedLine.startsWith("#")) {
					continue;
				}
				int indexOfEqual = trimmedLine.indexOf('=');
				if (indexOfEqual <= 0
						|| indexOfEqual == trimmedLine.length() - 1) {

					// illegal line, just skip
					continue;
				}
				String roma = trimmedLine.substring(0, indexOfEqual).trim();
				String nihongo = trimmedLine.substring(indexOfEqual + 1).trim();
				pairMap.put(roma, nihongo);
			}
		} catch (IOException e) {

			// do nothing, emit exception
		} finally {
			IOUtils.closeQuietly(reader);
		}
		return pairMap;
	}

	/**
	 * Create
	 * 
	 * @param pairMap
	 */
	public RomaToNihongoConverter(Map<String, String> pairMap) {
		super();
		matchers = new ArrayList<PrefixMatcher>();
		separatePairsToMatchers(pairMap);
		matchers.add(new SokuonPrefixMatcher());
	}

	/**
	 * Separate pairs to matchers.
	 * 
	 * @param pairMap
	 */
	private void separatePairsToMatchers(Map<String, String> pairMap) {
		DictionaryPrefixMatcher fourCharsMatcher = new DictionaryPrefixMatcher(
				4);
		DictionaryPrefixMatcher threeCharsMatcher = new DictionaryPrefixMatcher(
				3);
		DictionaryPrefixMatcher twoCharsMatcher = new DictionaryPrefixMatcher(2);
		DictionaryPrefixMatcher oneCharMatcher = new DictionaryPrefixMatcher(1);

		for (Map.Entry<String, String> entry : pairMap.entrySet()) {
			switch (entry.getKey().length()) {
			case 1:
				oneCharMatcher.addMapping(entry);
				break;
			case 2:
				twoCharsMatcher.addMapping(entry);
				break;
			case 3:
				threeCharsMatcher.addMapping(entry);
				break;
			case 4:
				fourCharsMatcher.addMapping(entry);
				break;
			}
		}

		matchers.add(fourCharsMatcher);
		matchers.add(threeCharsMatcher);
		matchers.add(twoCharsMatcher);
		matchers.add(oneCharMatcher);
	}

	/**
	 * Convert.
	 * 
	 * @param roma
	 * @return
	 */
	public String convert(String roma) {
		int length = roma.length();
		StringBuilder builder = new StringBuilder();
		int i = 0;
		boolean matched = false;
		while (i < length) {
			matched = false;
			for (PrefixMatcher matcher : matchers) {
				if (matcher.check(roma, i, length)) {
					builder.append(matcher.match(roma, i));
					i += matcher.getStepLength();
					matched = true;
					break;
				}
			}
			if (!matched) {

				// failed to match
				builder.append(roma.substring(i));
				break;
			}
		}
		return builder.toString();
	}

}
